//
// Created by fazhehy on 2024/5/30.
//

#include "st7789.h"

/* Control Registers and constant codes */
#define ST7789_NOP     0x00
#define ST7789_SWRESET 0x01
#define ST7789_RDDID   0x04
#define ST7789_RDDST   0x09

#define ST7789_SLPIN   0x10
#define ST7789_SLPOUT  0x11
#define ST7789_PTLON   0x12
#define ST7789_NORON   0x13

#define ST7789_INVOFF  0x20
#define ST7789_INVON   0x21
#define ST7789_DISPOFF 0x28
#define ST7789_DISPON  0x29
#define ST7789_CASET   0x2A
#define ST7789_RASET   0x2B
#define ST7789_RAMWR   0x2C
#define ST7789_RAMRD   0x2E

#define ST7789_PTLAR   0x30
#define ST7789_COLMOD  0x3A
#define ST7789_MADCTL  0x36

/**
 * Memory Data Access Control Register (0x36H)
 * MAP:     D7  D6  D5  D4  D3  D2  D1  D0
 * param:   MY  MX  MV  ML  RGB MH  -   -
 *
 */

/* Page Address Order ('0': Top to Bottom, '1': the opposite) */
#define ST7789_MADCTL_MY  0x80
/* Column Address Order ('0': Left to Right, '1': the opposite) */
#define ST7789_MADCTL_MX  0x40
/* Page/Column Order ('0' = Normal Mode, '1' = Reverse Mode) */
#define ST7789_MADCTL_MV  0x20
/* Line Address Order ('0' = LCD Refresh Top to Bottom, '1' = the opposite) */
#define ST7789_MADCTL_ML  0x10
/* RGB/BGR Order ('0' = RGB, '1' = BGR) */
#define ST7789_MADCTL_RGB 0x00

#define ST7789_RDID1   0xDA
#define ST7789_RDID2   0xDB
#define ST7789_RDID3   0xDC
#define ST7789_RDID4   0xDD

/* Advanced options */
#define ST7789_COLOR_MODE_16bit 0x55    //  RGB565 (16bit)
#define ST7789_COLOR_MODE_18bit 0x66    //  RGB666 (18bit)

#define ABS(x) ((x) > 0 ? (x) : -(x))

static void st7789_write_8bit_data(uint8_t data)
{
    HAL_SPI_Transmit(&hspi1, &data, 1, 0xff);
}

static void st7789_write_8bit_data_array(uint8_t *data, uint32_t len)
{
//    HAL_SPI_Transmit(&hspi1, data, len, 0xff);
    HAL_SPI_Transmit_DMA(&hspi1, data, len);
    while (hspi1.hdmatx->State != HAL_DMA_STATE_READY)
    {}
}

static void st7789_write_command(uint8_t command)
{
    ST7789_CS(0);
    ST7789_DC(0);
    st7789_write_8bit_data(command);
    ST7789_CS(1);
}

static void st7789_write_data(uint8_t data)
{
    ST7789_CS(0);
    ST7789_DC(1);
    st7789_write_8bit_data(data);
    ST7789_CS(1);
}

void st7789_write_data_array(uint8_t * buffer, uint32_t len)
{
    ST7789_CS(0);
    ST7789_DC(1);
    st7789_write_8bit_data_array(buffer, len);
    ST7789_CS(1);
}

void st7789_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
    ST7789_CS(0);
    uint16_t x_start = x0 + 0, x_end = x1 + 0;
    uint16_t y_start = y0 + 20, y_end = y1 + 20;

    /* Column Address set */
    st7789_write_command(ST7789_CASET);
    {
        uint8_t data[] = {x_start >> 8, x_start & 0xFF, x_end >> 8, x_end & 0xFF};
        st7789_write_data_array(data, sizeof(data));
    }

    /* Row Address set */
    st7789_write_command(ST7789_RASET);
    {
        uint8_t data[] = {y_start >> 8, y_start & 0xFF, y_end >> 8, y_end & 0xFF};
        st7789_write_data_array(data, sizeof(data));
    }
    /* Write to RAM */
    st7789_write_command(ST7789_RAMWR);
    ST7789_CS(1);
}

void st7789_init()
{
//    ST7789_BLK(1);

    ST7789_RST(1);
    HAL_Delay(25);
    ST7789_RST(0);
    HAL_Delay(25);
    ST7789_RST(1);
    HAL_Delay(50);

    st7789_write_data(0x00);
    st7789_write_command(0x11);
    HAL_Delay(120);

    st7789_write_command(0x36);
    st7789_write_data(0xa0);

    st7789_write_command(0x3A);
    st7789_write_data(0x05);

    st7789_write_command(0xB2);				//	Porch control
    {
        uint8_t data[] = {0x0C, 0x0C, 0x00, 0x33, 0x33};
        st7789_write_data_array(data, sizeof(data));
    }

//    ST7789_SetRotation(ST7789_ROTATION);	//	MADCTL (Display Rotation)
    st7789_write_command(ST7789_MADCTL);	// MADCTL
    st7789_write_data(ST7789_MADCTL_MX | ST7789_MADCTL_MY | ST7789_MADCTL_RGB);
//    st7789_write_data(ST7789_MADCTL_RGB);

//    st7789_write_command(0xB7);
//    st7789_write_data(0x35);

    st7789_write_command(0XB7);				//	Gate Control
    st7789_write_data(0x35);			//	Default value
    st7789_write_command(0xBB);				//	VCOM setting
    st7789_write_data(0x19);			//	0.725v (default 0.75v for 0x20)
    st7789_write_command(0xC0);				//	LCMCTRL
    st7789_write_data(0x2C);			//	Default value
    st7789_write_command(0xC2);				//	VDV and VRH command Enable
    st7789_write_data(0x01);			//	Default value
    st7789_write_command(0xC3);				//	VRH set
    st7789_write_data(0x12);			//	+-4.45v (defalut +-4.1v for 0x0B)
    st7789_write_command(0xC4);				//	VDV set
    st7789_write_data(0x20);			//	Default value
    st7789_write_command(0xC6);				//	Frame rate control in normal mode
    st7789_write_data(0x0F);			//	Default value (60HZ)
    st7789_write_command(0xD0);				//	Power control
    st7789_write_data(0xA4);			//	Default value
    st7789_write_data(0xA1);			//	Default value

    st7789_write_command(0xE0);
    {
        uint8_t data[] = {0xD0, 0x00, 0x03, 0x09, 0x13, 0x1C, 0x3A, 0x55, 0x48, 0x18, 0x12, 0x0E, 0x19, 0x1E};
        st7789_write_data_array(data, sizeof(data));
    }

    st7789_write_command(0xE1);
    {
        uint8_t data[] = {0xD0, 0x00, 0x03, 0x09, 0x05, 0x25, 0x3A, 0x55, 0x50, 0x3D, 0x1C, 0x1D, 0x1D, 0x23};
        st7789_write_data_array(data, sizeof(data));
    }

    st7789_write_command(0x29);
    st7789_write_command(0x2c);

    HAL_Delay(50);
    st7789_write_command(ST7789_INVON);		//	Inversion ON
//    st7789_fill_color(ST7789_WHITE);				//	Fill with Black.
    st7789_clear();
}

uint16_t disp_buf[ST7789_WIDTH*5];

void st7789_fill_color(uint16_t color)
{
    st7789_set_window(0, 0, ST7789_WIDTH - 1, ST7789_HEIGHT - 1);
    ST7789_CS(0);

    for (int i = 0; i < ST7789_HEIGHT / 5; i++)
    {
        memset(disp_buf, color, sizeof(disp_buf));
        st7789_write_data_array((uint8_t *)disp_buf, sizeof(disp_buf));
    }

//    for (uint16_t i = 0; i < ST7789_WIDTH; ++i) {
//        for (uint16_t j = 0; j < ST7789_HEIGHT; ++j) {
//            uint8_t data[] = {color >> 8, color & 0xFF};
//            st7789_write_data_array(data, sizeof(data));
//        }
//    }
    ST7789_CS(1);
}

void st7789_clear()
{
    st7789_fill_color(ST7789_BG_COLOR);
}

void st7789_draw_point(uint16_t x, uint16_t y, uint16_t color)
{
    if ((x >= ST7789_WIDTH) || (y >= ST7789_HEIGHT))
        return;

    st7789_set_window(x, y, x, y);
    uint8_t data[] = {color >> 8, color & 0xFF};
    ST7789_CS(0);
    st7789_write_data_array(data, sizeof(data));
    ST7789_CS(1);
}

void st7789_fill(uint16_t x_start, uint16_t y_start, uint16_t x_end, uint16_t y_end, uint16_t color)
{
    if ((x_end >= ST7789_WIDTH) || (y_end >= ST7789_HEIGHT))
        return;

    ST7789_CS(0);
    uint16_t i, j;
    st7789_set_window(x_start, y_start, x_end, y_end);
    for (i = y_start; i <= y_end; i++)
        for (j = x_start; j <= x_end; j++) {
            uint8_t data[] = {color >> 8, color & 0xFF};
            st7789_write_data_array(data, sizeof(data));
        }
    ST7789_CS(1);
}

void st7789_draw_big_point(uint16_t x, uint16_t y, uint16_t color)
{
    if ((x >= ST7789_WIDTH) || (y >= ST7789_HEIGHT))
        return;
    ST7789_CS(0);
    st7789_fill(x-1, y-1, x+1, y+1, color);
    ST7789_CS(1);
}

void st7789_draw_line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1,uint16_t color)
{
    uint16_t swap;
    uint16_t steep = ABS(y1 - y0) > ABS(x1 - x0);
    if (steep) {
        swap = x0;
        x0 = y0;
        y0 = swap;

        swap = x1;
        x1 = y1;
        y1 = swap;
        //_swap_int16_t(x0, y0);
        //_swap_int16_t(x1, y1);
    }

    if (x0 > x1) {
        swap = x0;
        x0 = x1;
        x1 = swap;

        swap = y0;
        y0 = y1;
        y1 = swap;
        //_swap_int16_t(x0, x1);
        //_swap_int16_t(y0, y1);
    }

    int16_t dx, dy;
    dx = x1 - x0;
    dy = ABS(y1 - y0);

    int16_t err = dx / 2;
    int16_t ystep;

    if (y0 < y1) {
        ystep = 1;
    } else {
        ystep = -1;
    }

    for (; x0<=x1; x0++) {
        if (steep) {
            st7789_draw_point(y0, x0, color);
        } else {
            st7789_draw_point(x0, y0, color);
        }
        err -= dy;
        if (err < 0) {
            y0 += ystep;
            err += dx;
        }
    }
}

void st7789_draw_vLine(uint16_t x, uint16_t y, uint16_t len, uint16_t color)
{
    for (int i = 0; i < len; ++i) {
        st7789_draw_point(x, y+i, color);
    }
}

void st7789_draw_hLine(uint16_t x, uint16_t y, uint16_t len, uint16_t color)
{
    for (int i = 0; i < len; ++i) {
        st7789_draw_point(x+i, y, color);
    }
}

void st7789_draw_rectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color)
{
    st7789_draw_hLine(x, y, w, color);
    st7789_draw_hLine(x, y+h-1, w, color);
    st7789_draw_vLine(x, y, h, color);
    st7789_draw_vLine(x+w-1, y, h, color);
//    uint16_t len =
//    st7789_draw_line(x1, y1, x2, y1, color);
//    st7789_draw_line(x1, y1, x1, y2, color);
//    st7789_draw_line(x1, y2, x2, y2, color);
//    st7789_draw_line(x2, y1, x2, y2, color);
}

void st7789_draw_filled_rectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color)
{
    st7789_fill(x, y, x+w, y+h, color);
}

void st7789_draw_circle(uint16_t x0, uint16_t y0, uint8_t r, uint16_t color)
{
    int16_t f = 1 - r;
    int16_t ddF_x = 1;
    int16_t ddF_y = -2 * r;
    int16_t x = 0;
    int16_t y = r;

    st7789_draw_point(x0, y0 + r, color);
    st7789_draw_point(x0, y0 - r, color);
    st7789_draw_point(x0 + r, y0, color);
    st7789_draw_point(x0 - r, y0, color);

    while (x < y) {
        if (f >= 0) {
            y--;
            ddF_y += 2;
            f += ddF_y;
        }
        x++;
        ddF_x += 2;
        f += ddF_x;

        st7789_draw_point(x0 + x, y0 + y, color);
        st7789_draw_point(x0 - x, y0 + y, color);
        st7789_draw_point(x0 + x, y0 - y, color);
        st7789_draw_point(x0 - x, y0 - y, color);

        st7789_draw_point(x0 + y, y0 + x, color);
        st7789_draw_point(x0 - y, y0 + x, color);
        st7789_draw_point(x0 + y, y0 - x, color);
        st7789_draw_point(x0 - y, y0 - x, color);
    }
}

void st7789_draw_circle_helper(uint16_t x0, uint16_t y0, uint16_t r, uint8_t cornername, uint16_t color)
{
    int f = 1 - r;
    int ddF_x = 1;
    int ddF_y = -2 * r;
    int x_ = 0;
    int y_ = r;
    while (x_<y_)
    {
        if (f >= 0)
        {
            y_--;
            ddF_y += 2;
            f += ddF_y;
        }
        x_++;
        ddF_x += 2;
        f += ddF_x;
        if (cornername & 0x04)
        {
            st7789_draw_point(x0 + x_, y0 + y_, color);
            st7789_draw_point(x0 + y_, y0 + x_, color);
        }
        if (cornername & 0x02)
        {
            st7789_draw_point(x0 + x_, y0 - y_, color);
            st7789_draw_point(x0 + y_, y0 - x_, color);
        }
        if (cornername & 0x08)
        {
            st7789_draw_point(x0 - y_, y0 + x_, color);
            st7789_draw_point(x0 - x_, y0 + y_, color);
        }
        if (cornername & 0x01)
        {
            st7789_draw_point(x0 - y_, y0 - x_, color);
            st7789_draw_point(x0 - x_, y0 - y_, color);
        }
    }
}

void st7789_draw_filled_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color)
{
    int f = 1 - r;
    int ddF_x = 1;
    int ddF_y = -2 * r;
    int x_ = 0;
    int y_ = r;
    while (x_ < y_) {
        if (f >= 0) {
            y_--;
            ddF_y += 2;
            f += ddF_y;
        }

        x_++;
        ddF_x += 2;
        f += ddF_x;

        st7789_draw_vLine(x0 + x_, y0 - y_, 2 * y_ + 1, color);
        st7789_draw_vLine(x0 + y_, y0 - x_, 2 * x_ + 1, color);
        st7789_draw_vLine(x0 - x_, y0 - y_, 2 * y_ + 1, color);
        st7789_draw_vLine(x0 - y_, y0 - x_, 2 * x_ + 1, color);
    }
    st7789_draw_vLine(x0, y0-r, 2*r+1, color);
}

void st7789_draw_filled_circle_helper(uint16_t x, uint16_t y, uint16_t r, uint16_t cornername, uint8_t delta, uint16_t color)
{
    int f = 1 - r;
    int ddF_x = 1;
    int ddF_y = -2 * r;
    int x_ = 0;
    int y_ = r;
    while (x_ < y_)
    {
        if (f >= 0)
        {
            y_--;
            ddF_y += 2;
            f += ddF_y;
        }

        x_++;
        ddF_x += 2;
        f += ddF_x;

        if (cornername & 0x1)
        {
            st7789_draw_vLine(x+x_, y-y_, 2*y_+1+delta, color);
            st7789_draw_vLine(x+y_, y-x_, 2*x_+1+delta, color);
        }

        if (cornername & 0x2)
        {
            st7789_draw_vLine(x-x_, y-y_, 2*y_+1+delta, color);
            st7789_draw_vLine(x-y_, y-x_, 2*x_+1+delta, color);
        }
    }
}

void st7789_draw_round_rectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t r, uint16_t color)
{
    st7789_draw_hLine(x+r, y, w-2*r, color); // Top
    st7789_draw_hLine(x+r, y+h-1, w-2*r, color); // Bottom
    st7789_draw_vLine(x, y+r, h-2*r, color); // Left
    st7789_draw_vLine(x+w-1, y+r, h-2*r, color); // Right

    st7789_draw_circle_helper(x+r, y+r, r, 1, color);
    st7789_draw_circle_helper(x+w-r-1, y+r, r, 2, color);
    st7789_draw_circle_helper(x+w-r-1, y+h-r-1, r, 4, color);
    st7789_draw_circle_helper(x+r, y+h-r-1, r, 8, color);
}

void st7789_draw_filled_round_rectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t r, uint16_t color)
{
    st7789_draw_filled_rectangle(x+r, y, w-2*r, h+1, color);
    st7789_draw_filled_circle_helper(x+w-r-1, y+r, r, 1, h-2*r-1, color);
    st7789_draw_filled_circle_helper(x+r, y+r, r, 2, h-2*r-1, color);
}

font_t ascii_8x16 = {
        .data = (uint8_t *)ascii_font_8x16,
        .width = 8,
        .height = 16
};


pFont st7789_font_8x16 = &ascii_8x16;

void st7789_show_char(uint16_t x, uint16_t y, pFont font, char ch)
{
    ST7789_CS(0);
    st7789_set_window(x, y, x+font->width-1, y+font->height-1);
    uint32_t b;
    uint16_t temp = font->width < 8 ? font->width : font->height;
    uint8_t pen_color_data[] = {ST7789_PEN_COLOR >>8, ST7789_PEN_COLOR & 0xff};
    uint8_t bg_color_data[] = {ST7789_BG_COLOR >>8, ST7789_BG_COLOR & 0xff};
    for (uint16_t i = 0; i < font->height/8; ++i) {
        for (uint16_t j = 0; j < 8; ++j) {
            for (uint16_t k = 0; k < font->width; ++k) {
                b = font->data[(ch-32)*temp+k+i*8];
                if ((b >> j) & 0x01){
                    st7789_write_data_array(pen_color_data, sizeof(pen_color_data));
                }
                else{
                    st7789_write_data_array(bg_color_data, sizeof(bg_color_data));
                }
            }
        }
    }
    ST7789_CS(1);
}

void st7789_show_string(uint16_t x, uint16_t y, const char *str)
{
    pFont font = st7789_font_8x16;
    while (*str){
        if (x + font->width >= ST7789_WIDTH || *str == '\n'){
            x = 0;
            y += font->height+2;
            if (y + font->height >= ST7789_HEIGHT)
                break;
            str ++;
            continue;
        }
        st7789_show_char(x, y, font, *str);
        x += font->width;
        str ++;
    }
}

uint8_t st7789_printf(uint16_t x, uint16_t y, const char * format, ...)
{
    char loc_buf[128];
    char * temp = loc_buf;
    va_list arg;
    va_list copy;
    va_start(arg, format);
    va_copy(copy, arg);
    int len = vsnprintf(temp, sizeof(loc_buf), format, copy);
    va_end(copy);
    if(len < 0) {
        va_end(arg);
        return 0;
    }
    va_end(arg);
    st7789_show_string(x, y, temp);
    return len;
}

